package org.fjsei.yewu.jpa;

import com.querydsl.core.types.Predicate;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.query.FluentQuery;

import java.util.List;
import java.util.function.Function;


/**投影和QueryDsl集成，自定义修改部分底层库的功能： 要极度提高性能,避免CPU计算能力浪费在没实际用处的东东上。
 * 【目的】支持投影 + 过滤条件 + no count() 三者功能。   spring和Jpa的版本升级注意改造代码兼容?
若UnitRepository extends QuerydslNcExecutor<Unit>,就算@EnableJpaRepositories(repositoryFactoryBeanClass没有设置也不会报错的！
感觉是纯粹动态的：repositoryFactoryBeanClass不设置,就会导致findBy()实际运行进入未自定制改造之前的流程，还会执行count()语句的，没有报错；
所以:只有配置EnableJpaRepositories(的才会真正的让findBy()进入我这自定义的代码中，才能省掉count()语句。
 目前有findBy三个接口支持模式，3个各有优劣。class-based DTO not supported: findBy适合于读取不会修改场合，读取后想要当前事务内修改的还需要提取Entity才行。
【实际上】graphQL的内省能力和这里相互冲突！要想跑得快就能尽可能定制好已经准备读取的字段和关联多层实体模型的字段，尽量避免graphQL客户端来自定义提取某些字段。服务端就限制死了才能提高性能。
 spring for graphQL又不能主动动态的修正投影Projection；所以看只能是相互矛盾的策略。只好是依据业务定制最终的优化代码; 这就像数据库调整表索引需求一样,动态修改适配代码。
* */
@NoRepositoryBean
public interface QuerydslNcExecutor<T> extends QuerydslPredicateExecutor<T> {
    //不需要做count(*)统计，比缺省的findAll()提高性能百倍。
    /*
        下面findBy就能替换这个功能: 把pageable放入参数：()->{.page()}函数中。
    * */
    //  Page<T> findAllNc(Predicate predicate, Pageable pageable);
    //? 旧的说法 ?无法直接利用findAll这个接口名称的，只能另外自定义findAllNc。

    /*不修改采用基类方案：最差，会做count(*)统计，1+N条查询，全部字段的：#还有一种什么接口函数都不要自己写的完全交给底层spring-graphql做：没有分页，过滤受约束，全部字段的,没count,有Left Join；
    限制来自spring-data-commons/org/springframework/data/repository/query/FluentQuery.java:155 Page<T> page(Pageable pageable)必须Page<T>类型;
    追踪 spring-data-jpa/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java:171 看来只能readPage()这里修改;
    最后决定在 FetchableFluentQueryByPredicate 的216行这个 Page<R> readPage(Pageable pageable) 特别定制。
    基类# 旧的是 <S extends T, R> R findBy（）;  S是实体T扩展子类型
    R 是返回类型 投影
    * */
    //<S extends T, R> R findBy(Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);


    /**单层极简方式： 仅仅能支持单层interface投影; 直接根据resultType类型所有的字段生成查询全部字段，
     * 不支持第二层的的字段过滤性质投影查询， 只能算是全部查了。
     * 参数pifType: 不能是普通的@Entity注解类{必须是投影一部分的字段}，只能传入interface接口投影的类；
     * 【局限】Class-based DTOs are not yet supported; 无法把返回的代理类直接转换JPA实体类,无法发挥用处：读interface之外的关联字段，事务之内保存Entity。
     * 泛型<I> ?问题:  <S>和<I>一样?
    * */
    <S extends T, R, I> R findBy(Class<I> pifType,Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);

    /**多层复杂方式：直接上字符串模式的投影：会支持第二层以上的的字段:  "A.B.C"
     * 麻烦不好用：fileds 投影的字段 文本串，【毛病】编译器无法检测错误，很容易隐藏错误书写字段。
     * */
    <S extends T, R> R findBy(List<String> fileds,Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);

    /**两层简易方式： 顶多支持两层嵌套关联的投影。
     * 可惜不支持3层或更多层次的。  @因为，若层次嵌套更深，很多层太复杂啊；
     * 这里查询结果，若是能上Unit实体类的，虽然查到的并不是全部的字段都有数据。但是并不影响把这些对象实例直接用于JPA其它的操作，底层会自动查询所有的最新字段数据的。
     *  pathSel=new QBeanMy<Unit>(Unit.class, q.id,q.indCod,q.cancel,q.area,q.company.id,q.company.name ,q.person.id,q.person.name);
     *  pathSel.bindLeftJoin(q.company,q.person);
     *  这里泛型， S:是T的投影interface类；  而 T:是JPA实体类(基准)。 而返回类型 R：一般就是比如Slice<S>的类型。
     *  pathSel: 照样也是投影一部分字段。
     * */
    <S extends T, R> R findBy(QBeanMy<?> pathSel, Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
}

